/*
 * Copyright (C) Francisco Morero Peyrona. All rights reserved.
 *
 * This software is published under the terms of Open Source
 * License version 1.1, a copy of which has been included with this
 * distribution in the License.txt file.
 */

package jdbcmanager.print.preview;

import java.awt.*;
import java.awt.print.*;
import java.awt.image.*;

import javax.swing.*;

import jdbcmanager.app.*;
import jdbcmanager.swing.*;

/**
 * Creates the print-preview panel (thubnails of pages to be printed).
 * <p>
 * Pages are scaled and they use the PageFormat pased in constructor.
 * 
 * @author Francisco Morero Peyrona
 */

public final class PrintPreviewPanel extends JPanel
{
   private final static int nHGAP = 16;
   private final static int nVGAP = 10;

   private Printable    source;
   private PageFormat   pgFormat;
   private Dimension    sourceSize;  // Size of source (only when it is an instance or a sub-class of Component)
   private double       nScale;      // In centil punctuation --> 50% == 0.5
   
   //-------------------------------------------------------------------------//
   
   /**
    * Class constructor.
    * 
    * @param source  An object that implements <code>Printable</code> interface
    */
   public PrintPreviewPanel( Printable source )
   {
      this( source, null, null );
   }
   
   /**
    * Class constructor.
    *  
    * @param source    An object that implements <code>Printable</code> interface
    * @param pgFormat  An instance of <code>PageFormat</code>
    */
   public PrintPreviewPanel( Printable source, PageFormat pgFormat )
   {
      this( source, pgFormat, null );
   }
   
   /**
    * Class constructor.
    * 
    * @param source      An object that implements <code>Printable</code> interface
    * @param pgFormat    An instance of <code>PageFormat</code>
    * @param sourceSize  si != null se buscan las pag horz y vert, sino no (must be passed if it is not an "image" like desktop).
    */
   public PrintPreviewPanel( Printable source, PageFormat pgFormat, Dimension sourceSize )
   {
      this.source      = source;
      this.pgFormat    = (pgFormat == null) ? PrinterJob.getPrinterJob().defaultPage() : pgFormat;
      this.nScale      = 0.5;
      this.sourceSize  = sourceSize;
      
      setLayout( new SpringLayout() );
      
      doPreview();
   }
   
   /**
    * Changes the PageFormat.
    * <p>
    * Note: There is no getPageFormat() --> this attribute is set-only.
    * 
    * @param pf  New page format to be used.
    */
   public void setPageFormat( PageFormat pf )
   {
      this.pgFormat = pf;
      clearPages();
      doPreview();
      doScale();
   }
   
   /**
    * Return current scale.
    * <p>
    * The number represents a percentage of original size.  
    * 
    * @return current scale.
    */
   public int getScale()
   {
      return (int) (this.nScale * 100);
   }
   
   /**
    * Changes current scale.
    * <p>
    * The number represents a percentage of original size: 100 == 100%
    * 
    * @param nPercentage
    */
   public void setScale( int nPercentage )
   {
      if( ((int) (this.nScale * 100)) != nPercentage )
      {
         this.nScale = nPercentage / 100.0;
         doScale();
      }
   }
   
   /**
    * Needed to be redefined.
    */
   public Dimension getPreferredSize()
   {
      Dimension dim  = null;
      int       nCmp = getComponentCount();

      if( nCmp == 0 )
      {
         dim = new Dimension( nHGAP, nVGAP );
      }
      else
      {
         Dimension dCmp    = getComponent(0).getPreferredSize();
         Dimension dParent = getParent().getSize();
         int       nCols;
         int       nRows;

         if( this.sourceSize == null )  // As this.sourceSize was not sat, standard rows and cols will be used
         {
            nCols = Math.max( (dParent.width - nHGAP) / (dCmp.width + nHGAP), 1 );
            nRows = nCmp / nCols;

            if( nRows * nCols < nCmp )
               nRows++;
         }
         else    // The number of rows and columns has to be found based on this.sourceSize 
         {
            Dimension dimPagesMatrix = getPagesMatrix();
            
            nCols = (int) dimPagesMatrix.getWidth();
            nRows = (int) dimPagesMatrix.getHeight();
         }

         Insets ins = getInsets();
         int    w   = nCols * (dCmp.width  + nHGAP) + nHGAP;
         int    h   = nRows * (dCmp.height + nVGAP) + nVGAP;
         dim        = new Dimension( w + ins.left + ins.right, h + ins.top + ins.bottom );
      }

      return dim;
   }

   /**
    * Needed to be redefined.
    */
   public Dimension getMaximumSize()
   {
      return getPreferredSize();
   }

   /**
    * Needed to be redefined.
    */
   public Dimension getMinimumSize()
   {
      return getPreferredSize();
   }

   //-------------------------------------------------------------------------//
   
   /**
    * As pages are added to the container, when the PageFormat changes, 
    * pages has to be deleted before adding all them again. 
    */
   private void clearPages()
   {
      int nComp = getComponentCount();

      if( nComp != 0 )
      {
         for( int n = 0; n < nComp; n++ )
         {
            if( getComponent( 0 ) instanceof PagePreview )
                remove( getComponent( 0 ) );
         }

         repaint();
      }
   }
   
   /**
    * Scales the thumbnails
    */
   private void doScale()
   {
      int nWidth  = (int)(this.pgFormat.getWidth()  * this.nScale);
      int nHeight = (int)(this.pgFormat.getHeight() * this.nScale);
      Component[] aComp = getComponents();

      for( int n = 0; n < aComp.length; n++ )
      {
         if( aComp[n] instanceof PagePreview )
            ((PagePreview) aComp[n]).setScaledSize( nWidth, nHeight );
      }
      
      doLayout();
      getParent().getParent().validate();  // getParent() == JViewport // getParent().getParent() == JScrollPane
      
      // Resizes and reposition parent (normally a JDialog) in screen
      Window parent = SwingUtilities.windowForComponent( this );
      parent.pack();
      
      GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
      Rectangle           r  = ge.getMaximumWindowBounds();  
      
      if( parent.getWidth() > r.getWidth() )
         parent.setSize( (int) r.getWidth(), parent.getHeight() );
      
      if( parent.getHeight() > r.getHeight() )
         parent.setSize( parent.getWidth(), (int) r.getHeight() );
      
      if( parent.getX() + parent.getWidth() > r.getWidth() )   
         parent.setLocation( (int) r.getX(), parent.getY() );
      
      if( parent.getY() + parent.getHeight() > r.getHeight() )
         parent.setLocation( parent.getX(), (int) r.getY() );
   }
   
   /**
    * Creates the thumbnails 
    */
   private void doPreview()
   {
      if( this.pgFormat.getHeight() <= 0 || this.pgFormat.getWidth() <= 0 )
      {
         App.showMessage( "Printing Problem", "Invalid page size: can't print" );  
      }
      else
      {
         int nPageWidth  = (int)(this.pgFormat.getWidth());
         int nPageHeight = (int)(this.pgFormat.getHeight());
         int nWidth      = (int)(nPageWidth  * this.nScale);
         int nHeight     = (int)(nPageHeight * this.nScale);
         int pgIndex     = 0;

         try
         {
            BufferedImage image = new BufferedImage( nPageWidth, nPageHeight, BufferedImage.TYPE_INT_RGB );
            Graphics      graph = image.getGraphics();
            
            graph.setColor( Color.white );
            graph.fillRect( 0, 0, nPageWidth, nPageHeight );

            while( this.source.print( graph, this.pgFormat, pgIndex ) != Printable.NO_SUCH_PAGE )
            {
                PagePreview pp = new PagePreview( nWidth, nHeight, image );
                add( pp );
                pgIndex++;

                image = new BufferedImage( nPageWidth, nPageHeight, BufferedImage.TYPE_INT_RGB );
                graph = image.getGraphics();
                graph.setColor( Color.white );
                graph.fillRect( 0, 0, nPageWidth, nPageHeight );
            }
            
            Dimension matrix = getPagesMatrix();
            
            SpringUtilities.makeCompactGrid( this, matrix.height, matrix.width, nHGAP, nVGAP, nHGAP, nVGAP );
         }
         catch( PrinterException exc )
         {
            App.showException( exc );
         }
      }
   }
   
   /**
    * Finds number of pages needed to print an object of size this.sourceSize 
    * (normally an instance of <code>Component</code> or any sub-class of <code>Component</code>).
    * 
    * @return An instance of <code>Dimension</code> class where:
    *         <ul> 
    *         <li><code>Dimension.width</code> is the number of pages needed in horizontal (number of columns)
    *         <li><code>Dimension.height</code> is the number of pages needed in vertical (number of rows)
    *         <li><code>Dimension.width * Dimension.height</code> is the total number of pages needed.
    *         </ul>
    */
   private Dimension getPagesMatrix()
   {
      int nPrnWidth  = (int) Math.round( this.pgFormat.getImageableWidth()  );        // Printable width  area
      int nPrnHeight = (int) Math.round( this.pgFormat.getImageableHeight() );        // Printable height area                                        
      int nHorzPages = (int) Math.ceil( this.sourceSize.getWidth()  / nPrnWidth  );   // Total number of horizontal pages (columns) 
      int nVertPages = (int) Math.ceil( this.sourceSize.getHeight() / nPrnHeight );   // Total number of vertical   pages (rows)

      return new Dimension( nHorzPages, nVertPages );
   }
}